﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CodeAnalysisRuleCollection.Common.CSharp
{
    /// <example>var a = 1;</example>
    [ExportCodeRefactoringProvider("ExplicitTyping", LanguageNames.CSharp)]
    internal sealed class ExplicitTypingCodeRefactoringProvider : ICodeRefactoringProvider
    {
        public async Task<IEnumerable<CodeAction>> GetRefactoringsAsync(Document document, TextSpan span, CancellationToken cancellationToken)
        {
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            if ((var variable = root.FindToken(span.Start).Parent.FirstAncestorOrSelf<VariableDeclarationSyntax>()) != null)
            {
                if (variable.Type != null && variable.Type.IsVar && variable.DescendantNodes().OfType<ExpressionSyntax>().Any())
                    return new[] { CodeAction.Create("Use explicit typing", c => Fix(document,  variable, cancellationToken)) };
            }

            return null;
        }

        private async Task<Document> Fix(Document document, VariableDeclarationSyntax literal, CancellationToken cancellationToken)
        {
            var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var expression = literal.DescendantNodes().OfType<ExpressionSyntax>().FirstOrDefault();
            var type = model.GetTypeInfo(expression, cancellationToken).Type;

            var oldNode = literal;
            var newNode = oldNode
                .WithType(
                    SyntaxFactory.ParseTypeName(type.ToDisplayString())
                        .WithAdditionalAnnotations(Simplifier.Annotation)
                )
                .WithAdditionalAnnotations(Formatter.Annotation)
            ;

            SyntaxNode newRoot = syntaxRoot.ReplaceNode(oldNode, newNode);
            return document.WithSyntaxRoot(newRoot);
        }
    }
}
